package org.annoflow.javassist;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * The Class Macro. This abstract class represents a macro defined in a
 * configuration file. Note, all parameters in the macro body must be prefixed
 * by a %.
 * 
 * define <Macro Name>(<Parameters>) <Macro Body>
 * 
 * The macros are then used in other command bodies like so:
 * 
 * %<MacroName>(<Parameters>)
 * 
 * 
 * 
 */
public abstract class Macro
{

	public class MalformedMacroParametersException extends
			MalformedMacroException
	{

		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;

		public MalformedMacroParametersException(String macro)
		{
			super("Unable to parse parameters in " + macro);
		}

	}

	/** The macro. */
	String macro;

	/** The macro name. */
	String macroName;

	/** The macro params. */
	List<String> macroParams;

	/** The macro name pat. */
	Pattern macroNamePat;

	/** The param pat. */
	Pattern paramPat;

	/**
	 * Instantiates a new macro. This constructor allows a user to define a
	 * custom patter for parameters and names.
	 * 
	 * @param userMacro
	 *            the name of the user passed macro
	 * @param namePattern
	 *            the pattern used to identify the name
	 * @param paramPattern
	 *            the the pattern used to identify the parameters.
	 * 
	 * @throws MalformedMacroParametersException
	 *             the malformed macro parameters exception
	 */
	public Macro(String userMacro, String namePattern, String paramPattern)
			throws MalformedMacroException
	{
		macroNamePat = Pattern.compile(namePattern);
		paramPat = Pattern.compile(paramPattern);
		macro = userMacro;
		macroName = getMacroName(macro);
		macroParams = getParameters(macro);
	}

	/**
	 * Gets the number of parameters this macro requries.
	 * 
	 * @return the number of params
	 */
	public int getNumberOfParams()
	{
		return macroParams.size();
	}

	/**
	 * Gets the macro name.
	 * 
	 * @return the macro name
	 */
	public String getMacroName()
	{
		return macroName;
	}

	/**
	 * Gets the parameter iterator.
	 * 
	 * @return the parameter iterator
	 */
	public Iterator<String> getParameterIterator()
	{
		return macroParams.iterator();
	}

	private String getMacroName(String macro) throws MalformedMacroException
	{
		String mName = "";

		Matcher macroNameMatcher = macroNamePat.matcher(macro);
		// Matcher parameterMatcher = parameter.matcher(macro);
		if (macroNameMatcher.find())
		{
			mName = macroNameMatcher.group().substring(0,
					macroNameMatcher.end() - 1);
			macro = macro.substring(macroNameMatcher.end());
			// System.out.println(m.group());
		}
		else
		{
			System.err.println("Malformed Macro");
			throw new MalformedMacroException("Unable to find macro name in:  "
					+ macro);
		}
		return mName;
	}

	private List<String> getParameters(String macro)
			throws MalformedMacroParametersException
	{
		List<String> parameters = new ArrayList<String>();
		// Pattern paramPat = Pattern.compile("\\(.*\\)\\s*\\{");
		Matcher parameterSectionMatcher = paramPat.matcher(macro);
		String parameterSection = "";
		if (parameterSectionMatcher.find())
		{
			parameterSection = parameterSectionMatcher.group();
		}
		else
		{
			throw new MalformedMacroParametersException(macro);
		}
		// Pattern parameter = Pattern.compile("[^(%),.\\s]+[,|\\)]");
		parameterSection = parameterSection.trim().substring(1);
		Pattern parameter = Pattern.compile("[^\\s]+\\s*,");
		Matcher parameterMatcher = parameter.matcher(parameterSection);

		while (parameterMatcher.find())
		{
			String tempParam = parameterMatcher.group().trim();
			tempParam = tempParam.substring(0, tempParam.length() - 1);
			parameterSection = parameterSection.substring(
					parameterMatcher.end()).trim();
			parameterMatcher = parameter.matcher(parameterSection);
			parameters.add(tempParam);
		}
		Pattern lastParam = Pattern.compile("[^\\s]+\\s*\\)");
		Matcher lastParamMatch = lastParam.matcher(parameterSection);

		if (lastParamMatch.find())
		{
			String lParam = lastParamMatch.group();
			lParam = lParam.trim().substring(0, lParam.length() - 1);
			lParam = lParam.trim();
			parameters.add(lParam);
		}
		return parameters;
	}
}
